package com.efivestar.idm.api.util.weixin;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.efivestar.idm.api.util.weixin.bean.*;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.IMap;
import org.apache.catalina.util.URLEncoder;
import org.apache.commons.codec.digest.DigestUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;

import java.nio.charset.Charset;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.function.Function;

@Service
public class WeixinApi {

    public static final String WEIXIN_DOMAIN = "https://api.weixin.qq.com/";
    public static final String ACCESS_TOKEN_URL = WEIXIN_DOMAIN + "cgi-bin/token";
    public static final String SEND_TEMPLATE_MESSAGE_URL = WEIXIN_DOMAIN + "cgi-bin/message/template/send?access_token=%s";
    public static final String TICKET_API_URL = WEIXIN_DOMAIN + "cgi-bin/ticket/getticket?access_token=%s&type=jsapi";
    public static final String OAUTH2_CODE_URL = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=%s&state=%s#wechat_redirect";
    public static final String OAUTH2_TOKEN_URL = WEIXIN_DOMAIN + "sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code";

    public static final String ACCESS_TOKEN_CACHE_KEY = "accessToken";
    public static final String JS_TICKET_CACHE_KEY = "JSTicket";



    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private RestTemplate restTemplate;

    @Value("${spring.application.name}")
    private String appName;
    @Value("${weixin.appId}")
    private String appId;
    @Value("${weixin.secret}")
    private String secret;

    @Autowired
    private HazelcastInstance hazelcastInstance;

    public synchronized Token getAccessToken() {
        IMap<String, Token> map = hazelcastInstance.getMap(appName);
        Function<String, Token> getTokenFunction = (k) -> {
            HttpHeaders headers = new HttpHeaders();
            String url = String.format(ACCESS_TOKEN_URL + "?grant_type=client_credential&appid=%s&secret=%s", appId, secret);

            //  执行HTTP请求
            ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, null, String.class);
            String responseBody = response.getBody();
            JSONObject jsonObject = JSONObject.parseObject(responseBody);
            Integer errcode = jsonObject.getInteger("errcode");
            Token token = null;
            if (errcode != null){
                logger.error("获取accessToken失败", responseBody);
            } else {
                token = JSONObject.parseObject(responseBody, Token.class);
                token.setExpireTime(new Date((new Date()).getTime() + token.getExpiresIn()*1000L));
            }
            //  输出结果
            return token;
        };
        Token result = map.computeIfAbsent(ACCESS_TOKEN_CACHE_KEY, getTokenFunction);
        if (result != null) {
            long now = (new Date()).getTime();
            if (result.getExpireTime().getTime() - now < 300000) {//如果token距离失效时间还剩下5分钟，则重新获取一次
                map.delete(ACCESS_TOKEN_CACHE_KEY);
                Token newToken = getTokenFunction.apply(ACCESS_TOKEN_CACHE_KEY);
                map.put(ACCESS_TOKEN_CACHE_KEY, newToken);
                result = newToken;
            }
        }

//        Token result = new Token();
//        result.setAccessToken("xzPMqAoLPgtZp4xajnvDiRoBZTXUuWHdX9J5sjwuKNDqaRB2Xm-D0zwARB897J2UAX-FzDsBxoV6YS8Ez6gp4m78-tmCfFubN78uim1dJx4sGMuQmEkI7BzE22x3WvXwOWNdACATBS");
        logger.info("我获取到的AccessToken是：{}", result.getAccessToken());
        return result;
    }


    public synchronized JsTicket getJSTicket() {
        IMap<String, JsTicket> map = hazelcastInstance.getMap(appName);
        Function<String, JsTicket> getTicketFunction = (k) -> {
            //  也支持中文
            String url = String.format(TICKET_API_URL, getAccessToken().getAccessToken());

            //  执行HTTP请求
            ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, null, String.class);
            String responseBody = response.getBody();
            JSONObject jsonObject = JSONObject.parseObject(responseBody);
            Integer errcode = jsonObject.getInteger("errcode");
            boolean getJsTicketSuccess = false;
            JsTicket ticket = null;
            if (errcode != null){
                //发送失败
                if (errcode.intValue()  ==0 ) {
                    ticket = JSONObject.parseObject(responseBody, JsTicket.class);
                    ticket.setExpireTime(new Date((new Date()).getTime() + ticket.getExpiresIn()*1000L));
                    getJsTicketSuccess = true;
                }
            }
            if (!getJsTicketSuccess) {
                String errorMsg = jsonObject.getString("errmsg");
                logger.error("JSTicket获取失败：", responseBody);
                throw new RuntimeException("JSTicket获取失败："+ errorMsg);
            }
            //  输出结果
            return ticket;
        };
        JsTicket result = map.computeIfAbsent(JS_TICKET_CACHE_KEY, getTicketFunction);
        if (result != null) {
            long now = (new Date()).getTime();
            if (result.getExpireTime().getTime() - now < 300000) {//如果token距离失效时间还剩下5分钟，则重新获取一次
                map.delete(JS_TICKET_CACHE_KEY);
                JsTicket jsTicket = getTicketFunction.apply(JS_TICKET_CACHE_KEY);
                map.put(JS_TICKET_CACHE_KEY, jsTicket);
                result = jsTicket;
            }
        }
        return  result;
    }

    public  boolean sendTemplateMessage(String toUser, String templateId, String href, Map data, String topcolor) {
        Map templateMessage = new HashMap();
        templateMessage.put("touser", toUser);
        templateMessage.put("template_id", templateId);
        if (!StringUtils.isEmpty(href)) {
            templateMessage.put("url", href);
        }
        templateMessage.put("topcolor", topcolor);
        templateMessage.put("data", data);
        HttpHeaders headers = new HttpHeaders();
        MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8");
        headers.setContentType(type);
        headers.add("Accept", MediaType.APPLICATION_JSON.toString());
        String url = String.format(SEND_TEMPLATE_MESSAGE_URL, getAccessToken().getAccessToken());
        HttpEntity<String> formEntity = new HttpEntity<String>(JSONObject.toJSONString(templateMessage), headers);
        //  执行HTTP请求
        String responseBody = restTemplate.postForObject(url, formEntity, String.class);
        JSONObject jsonObject = JSONObject.parseObject(responseBody);
        Integer errcode = jsonObject.getInteger("errcode");
        Token token = null;
        if (errcode != null){
            //发送失败
            if (errcode.intValue() !=0 ) {
                logger.error("发送模板消息失败，,openId:{}, templateId:{}, 消息内容:{}，错误码:{} ", new Object[]{toUser, templateId, responseBody, errcode});
                return false;
            } else {
                logger.info("发送模板消息成功,openId:{}, templateId:{}, 消息内容:{}", new Object[]{toUser, templateId, responseBody});
            }
        }

        return true;
    }

    /**
     * 前端js签名获取.
     * @param url
     * @return
     */
    public JsTicketSignuature getJSTicketSignature(String url){
        String nocestr = UUID.randomUUID().toString();
        Long timestamp = System.currentTimeMillis();
        String jsapi_ticket = getJSTicket().getTicket();
//        String jsapi_ticket = "sM4AOVdWfPE4DxkXGEs8VIosCVU54Ok3UoAUSYeuYemcpMrBSZuxU0ZPUTI14k_9KvAd98Q_E6kXYlJgZJAgaQ";
        String subUrl = url.substring(0, url.indexOf("#")!= -1 ? url.indexOf("#") : url.length() );
        String signatureKey = String.format("jsapi_ticket=%s&noncestr=%s&timestamp=%s&url=%s", jsapi_ticket, nocestr, timestamp, subUrl);
        String signature = DigestUtils.sha1Hex(signatureKey);
        return JsTicketSignuature.builder().timestamp(timestamp).noncestr(nocestr).signature(signature).appId(appId).build();
    }

    /**
     * 可以通过下面方法生成的url,让微信执行一次跳转，把openID带回来。
     * @param url
     * @param responseTypeEnum
     * @param state
     * @return
     */
    public String getOAuth2AccessTokenUrl(String url, WeiXinOAuth2ScopeEnum responseTypeEnum, String state){
//        appid=%s&redirect_uri=%s&&response_type=code&scope=%s&state=%s#wechat_redirect";
        String encodeRedirectUrl = URLEncoder.QUERY.encode(url, Charset.defaultCharset());
        String oAuth2AccessTokenUrl = String.format(OAUTH2_CODE_URL, appId, encodeRedirectUrl, responseTypeEnum.getValue(), state);
        return oAuth2AccessTokenUrl;
    }


    /**
     * 获取Oauth2的AccessToken 该token中包含了openId.
     * @param code
     * @return
     */
    public OAuth2AccessToken getOAuth2AccessToken(String code){
        //    http://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
        //  也支持中文
        String url = String.format(OAUTH2_TOKEN_URL, appId, secret, code);
        //  执行HTTP请求
        ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, null, String.class);
        String responseBody = response.getBody();
        JSONObject jsonObject = JSONObject.parseObject(responseBody);
        Integer errcode = jsonObject.getInteger("errcode");
        OAuth2AccessToken oAuth2AccessToken = null;
        if (errcode != null) {
            logger.error("获取openId失败", responseBody);
            return null;
        } else {
            oAuth2AccessToken = JSON.parseObject(responseBody, OAuth2AccessToken.class);
        }
        return oAuth2AccessToken;
    }

}
